package eval.engine.openStack;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.inject.Inject;
import eval.deploy.DeploySet;
import eval.deploy.IDemandManager;
import eval.deploy.IDeployManager;
import eval.engine.BasicEngineModel;
import eval.engine.IPlacement;
import eval.engine.IPlacementEngine;
import eval.host.Host;
import eval.host.IHostManager;
import eval.host.compare.ISortHostList;
import eval.hostEvaluate.IHostEvaluate;
import eval.vm.IVMManager;
import eval.vm.MinCPURequiredComparator;
import eval.vm.VirtualMachine;




public class OpenStackEngine extends BasicEngineModel implements IPlacementEngine{

	
	/**
	 * @Constructor
	 * @param vmManager
	 * @param deployManager
	 * @param hostManager
	 * @param demandManager
	 * @param sortHostList
	 */
	@Inject
	public OpenStackEngine(IVMManager vmManager, IDeployManager deployManager,
			IHostManager hostManager, IDemandManager demandManager,
			ISortHostList sortHostList,IHostEvaluate hostEvaluator) {
		super(vmManager,deployManager,hostManager,demandManager,sortHostList,hostEvaluator);
	}
	
	@Override
	public IPlacement computePlacement(int tick, IPlacement lastPlacement) {
		//update start time
		engineStartTime = System.currentTimeMillis();
		this.tick =tick;
		//list of vm's that going to be deploy  
		List<VirtualMachine> placedNewVms =  new ArrayList<VirtualMachine>();
		//list of vm's that ask to deploy but rejected.
		List<VirtualMachine> rejectedVms =  new ArrayList<VirtualMachine>();
		//remove vm's list
		List<VirtualMachine> vmsToRemove = this.deployManager.getVMToBeRemovedAtTick(tick ,vmMapping);
		//deploy new vm list
		DeploySet deploySet = deployManager.getDeploySetAtTick(tick);
		//new deploy vm's
		List<VirtualMachine> vmsToDeploy = deploySet.getVMs();
		//CPU demand of vm's already on cloud
		Map<VirtualMachine, Integer> cpuDemandOfVMs = demandManager.getCPUDemandOfAllVMsAtTick(tick);
		//MEM demand of vm's already on cloud
		Map<VirtualMachine, Integer> memDemandOfVMs = demandManager.getMEMDemandOfAllVMsAtTick(tick);
		//remove vm from host
		removeVirtualMachines(vmsToRemove);
		//Initial vmMaping and cloud
		iniCloudMapping(lastPlacement,vmsToRemove,cpuDemandOfVMs,memDemandOfVMs);
		//validatePlacement();
		if (!deploySet.isEmpty()) {  
			//we need to deploy new vm's
			// Order in which VMs are considered
			int vmCpuLoad,vmMemLOad;
			Collections.sort(vmsToDeploy,Collections.reverseOrder(new MinCPURequiredComparator()));
			for (VirtualMachine vm : vmsToDeploy) {
				Host host = getFirstFitHost(vm,cpuDemandOfVMs,memDemandOfVMs,tick);
				if(host != null){
					vmCpuLoad = Math.min(cpuDemandOfVMs.get(vm).intValue(),vm.getVMConfiguration().getMinCpu());
					vmMemLOad = memDemandOfVMs.get(vm).intValue();
					vmManager.setSatisfiedLoad(vm, vmCpuLoad, vmMemLOad);
					hostManager.addVmToHost(host,vm,vmCpuLoad, vmMemLOad);
					deployManager.setVmStartLife(vm, tick);
					placedNewVms.add(vm);
				}
				else{
					//TODO:add exception - why reject 
					System.out.println("rejected vm: "+ vm.getIdentifier());
					rejectedVms.add(vm);
				}
			}
			
		}
	
		updateLoadOnCloud(cpuDemandOfVMs,memDemandOfVMs);
//		validatePlacement();
		updateVmMap();
		HistoryDemandArray[tick] = getTotalCpuDemand();
		//check if we need to open/close host
		if(!openNewHost())
			closeHost();
//		cpuReservedCheck();
		//create IPlacement
		validatePlacement();
		if(vmsToDeploy.size() != placedNewVms.size() +rejectedVms.size())
			System.out.println("Error at tick: "+tick);
		openStackPlacement newPlacement = new openStackPlacement(
				vmMapping, hostManager, lastPlacement, demandManager ,vmsToRemove,
				vmsToDeploy,placedNewVms,rejectedVms ,tick );
		//update end time
		engineStopTime = System.currentTimeMillis();
		return newPlacement;
	}
	

	@Override
	public double getEngineTimeInSec() {
		return engineStopTime - engineStartTime;
	}
	
	@Override
	public void resetEngineTime() {
		engineStartTime = engineStopTime = 0;
	}

}
